﻿using System;
using System.Globalization;

namespace Microsoft.Xna.Framework.Graphics
{
	/// <summary>
	/// Contains blend state for the device.
	/// </summary>
	public class BlendState : GraphicsResource
	{
		#region Fields
		/// <summary>
		/// A built-in state object with settings for additive blend that is adding the destination data to the source data without using alpha.
		/// </summary>
		public static readonly BlendState Additive = new BlendState(Blend.SourceAlpha, Blend.One, "BlendState.Additive");

		/// <summary>
		/// A built-in state object with settings for alpha blend that is blending the source and destination data using alpha.
		/// </summary>
		public static readonly BlendState AlphaBlend = new BlendState(Blend.One, Blend.InverseSourceAlpha, "BlendState.AlphaBlend");

		private BlendFunction alphaBlendFunction;
		private Blend alphaDestinationBlend;
		private Blend alphaSourceBlend;
		private Color blendFactor;
		private BlendFunction colorBlendFunction;
		private Blend colorDestinationBlend;
		private Blend colorSourceBlend;
		private ColorWriteChannels colorWriteChannels;
		private ColorWriteChannels colorWriteChannels1;
		private ColorWriteChannels colorWriteChannels2;
		private ColorWriteChannels colorWriteChannels3;
		internal bool isBound = false;
		private int multiSampleMask;

		/// <summary>
		/// A built-in state object with settings for blending with non-premultipled alpha that is blending source and destination data by using alpha while assuming the color data contains no alpha information.
		/// </summary>
		public static readonly BlendState NonPremultiplied = new BlendState(Blend.SourceAlpha, Blend.InverseSourceAlpha, "BlendState.NonPremultiplied");

		/// <summary>
		/// A built-in state object with settings for opaque blend that is overwriting the source with the destination data.
		/// </summary>
		public static readonly BlendState Opaque = new BlendState(Blend.One, Blend.Zero, "BlendState.Opaque");
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the arithmetic operation when blending alpha values. The default is <see cref="F:BlendFunction.Add"/>.
		/// </summary>
		public BlendFunction AlphaBlendFunction
		{
			get { return alphaBlendFunction; }
			set
			{
				ThrowIfBound();
				alphaBlendFunction = value;
			}
		}

		/// <summary>
		/// Gets or sets the blend factor for the destination alpha, which is the percentage of the destination alpha included in the blended result. The default is <see cref="F:Blend.One"/>.
		/// </summary>
		public Blend AlphaDestinationBlend
		{
			get { return alphaDestinationBlend; }
			set
			{
				ThrowIfBound();
				alphaDestinationBlend = value;
			}
		}

		/// <summary>
		/// Gets or sets the alpha blend factor. The default is <see cref="F:Blend.One"/>.
		/// </summary>
		public Blend AlphaSourceBlend
		{
			get { return alphaSourceBlend; }
			set
			{
				ThrowIfBound();
				alphaSourceBlend = value;
			}
		}

		/// <summary>
		/// Gets or sets the four-component (RGBA) blend factor for alpha blending.
		/// </summary>
		public Color BlendFactor
		{
			get { return blendFactor; }
			set
			{
				ThrowIfBound();
				blendFactor = value;
			}
		}

		/// <summary>
		/// Gets or sets the arithmetic operation when blending color values. The default is <see cref="F:BlendFunction.Add"/>.
		/// </summary>
		public BlendFunction ColorBlendFunction
		{
			get { return colorBlendFunction; }
			set
			{
				ThrowIfBound();
				colorBlendFunction = value;
			}
		}

		/// <summary>
		/// Gets or sets the blend factor for the destination color. The default is <see cref="F:Blend.One"/>.
		/// </summary>
		public Blend ColorDestinationBlend
		{
			get { return colorDestinationBlend; }
			set
			{
				ThrowIfBound();
				colorDestinationBlend = value;
			}
		}

		/// <summary>
		/// Gets or sets the blend factor for the source color. The default is <see cref="F:Blend.One"/>.
		/// </summary>
		public Blend ColorSourceBlend
		{
			get { return colorSourceBlend; }
			set
			{
				ThrowIfBound();
				colorSourceBlend = value;
			}
		}

		/// <summary>
		/// Gets or sets which color channels (RGBA) are enabled for writing during color blending. The default value is <see cref="F:ColorWriteChannels.None"/>.
		/// </summary>
		public ColorWriteChannels ColorWriteChannels
		{
			get { return colorWriteChannels; }
			set
			{
				ThrowIfBound();
				colorWriteChannels = value;
			}
		}

		/// <summary>
		/// Gets or sets which color channels (RGBA) are enabled for writing during color blending. The default value is <see cref="F:ColorWriteChannels.None"/>.
		/// </summary>
		public ColorWriteChannels ColorWriteChannels1
		{
			get { return colorWriteChannels1; }
			set
			{
				ThrowIfBound();
				colorWriteChannels1 = value;
			}
		}

		/// <summary>
		/// Gets or sets which color channels (RGBA) are enabled for writing during color blending. The default value is <see cref="F:ColorWriteChannels.None"/>.
		/// </summary>
		public ColorWriteChannels ColorWriteChannels2
		{
			get { return colorWriteChannels2; }
			set
			{
				ThrowIfBound();
				colorWriteChannels2 = value;
			}
		}

		/// <summary>
		/// Gets or sets which color channels (RGBA) are enabled for writing during color blending. The default value is <see cref="F:ColorWriteChannels.None"/>.
		/// </summary>
		public ColorWriteChannels ColorWriteChannels3
		{
			get { return colorWriteChannels3; }
			set
			{
				ThrowIfBound();
				colorWriteChannels3 = value;
			}
		}

		/// <summary>
		/// Gets or sets a bitmask which defines which samples can be written during multisampling. The default is 0xffffffff.
		/// </summary>
		public int MultiSampleMask
		{
			get { return multiSampleMask; }
			set
			{
				ThrowIfBound();
				multiSampleMask = value;
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Creates an instance of the BlendState class with default values, using additive color and alpha blending.
		/// </summary>
		public BlendState()
		{
			alphaBlendFunction = BlendFunction.Add;
			alphaDestinationBlend = Blend.Zero;
			alphaSourceBlend = Blend.One;
			blendFactor = Color.White;
			colorBlendFunction = BlendFunction.Add;
			colorDestinationBlend = Blend.Zero;
			colorSourceBlend = Blend.One;
			colorWriteChannels = ColorWriteChannels.All;
			colorWriteChannels1 = ColorWriteChannels.All;
			colorWriteChannels2 = ColorWriteChannels.All;
			colorWriteChannels3 = ColorWriteChannels.All;
			isBound = false;
			multiSampleMask = -1;
		}

		private BlendState(Blend sourceBlend, Blend destinationBlend, string name)
		{
			alphaBlendFunction = BlendFunction.Add;
			alphaDestinationBlend = destinationBlend;
			alphaSourceBlend = sourceBlend;
			blendFactor = Color.White;
			colorBlendFunction = BlendFunction.Add;
			colorDestinationBlend = destinationBlend;
			colorSourceBlend = sourceBlend;
			colorWriteChannels = ColorWriteChannels.All;
			colorWriteChannels1 = ColorWriteChannels.All;
			colorWriteChannels2 = ColorWriteChannels.All;
			colorWriteChannels3 = ColorWriteChannels.All;
			isBound = true;
			multiSampleMask = -1;
			base.Name = name;
		}
		#endregion

		private void ThrowIfBound()
		{
			if (this.isBound)
				throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, FrameworkResources.BoundStateObject, typeof(BlendState).Name));
		}
	}
}
